using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.Serialization;
using Bookvar.ObjectModel.Actions.Collaboration;
using Bookvar.ObjectModel.Collections;

namespace Bookvar.ObjectModel
{
    /// <summary>
    /// Represents the whole mind map.
    /// </summary>
    [SuppressMessage( "Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MindMap",
        Justification = "Rename MindMap to Mindmap latter, because it is error prone." )]
    [DataContract]
    public class MindMap : INotifyPropertyChanged
    {
        public const string MindmapDefaultFileName = "Untitled";
        [DataMember] private string fileName;
        private TopicCollection floatingTopics;
        private MainTopic mainTopic;
        private TopicPositionCollection positions;
        //private MapLayoutProperties properties;

        public MindMap()
            : this( new MainTopic(), new TopicPositionCollection() )
        {
        }

        public MindMap( MainTopic tree )
            : this( tree, new TopicPositionCollection() )
        {
        }

        [SuppressMessage( "Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors" )]
        public MindMap( MainTopic tree, TopicPositionCollection positions )
        {
            MainTopic = tree;
            //this.store = new Store();
            Key = Guid.NewGuid();
            fileName = String.Empty;
            this.positions = positions;
            //this.properties = new MapLayoutProperties();
            floatingTopics = new TopicCollection();
            floatingTopics.CollectionChanged += OnFloatingTopicsCollectionChanged;
        }

        [DataMember]
        public Guid Key { get; set; }

        [SuppressMessage( "Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly" )]
        [DataMember]
        public TopicCollection FloatingTopics
        {
            get { return floatingTopics; }
            set
            {
                floatingTopics = value;
                OnPropertyChanged( new PropertyChangedEventArgs( "FloatingTopics" ) );
            }
        }

        [SuppressMessage( "Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly" )]
        [DataMember]
        public TopicPositionCollection TopicPositions
        {
            get { return positions; }
            set
            {
                positions = value;
                OnPropertyChanged( new PropertyChangedEventArgs( "TopicPositions" ) );
            }
        }

        //[DataMember]
        //public MapLayoutProperties Properties
        //{
        //    get { return properties; }
        //    set { properties = value; }
        //}

        [DataMember]
        public string FileName
        {
            get
            {
                if ( String.IsNullOrEmpty( fileName ) )
                {
                    return MindmapDefaultFileName;
                }
                return Path.GetFileName( fileName );
            }
            set
            {
                fileName = value;
                OnPropertyChanged( new PropertyChangedEventArgs( "FileName" ) );
            }
        }

        public string FullPath
        {
            get { return fileName; }
        }

        [DataMember]
        public MainTopic MainTopic
        {
            get { return mainTopic; }
            set
            {
                if ( mainTopic != null )
                {
                    mainTopic.DoActionEvent -= MainTopic_ActionHappened;
                }

                if ( value != null )
                {
                    value.DoActionEvent += MainTopic_ActionHappened;
                }

                mainTopic = value;
                OnPropertyChanged( new PropertyChangedEventArgs( "MainTopic" ) );
            }
        }

        #region INotifyPropertyChanged Members

        [field : NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        [field : NonSerialized]
        public event EventHandler<ActionEventArgs> ActionHappened;

        private void MainTopic_ActionHappened( object sender, ActionEventArgs e )
        {
            if ( ActionHappened != null )
            {
                ActionHappened( this, e );
            }
        }

        public bool RemoveTopic( Topic topic )
        {
            if ( topic == null )
            {
                throw new ArgumentNullException( "topic" );
            }

            if ( topic.IsFloating )
            {
                return FloatingTopics.Remove( topic );
            }
            if ( topic.Parent != null )
            {
                return topic.Parent.SubTopics.Remove( topic );
            }
            return false;
        }

        /// <summary>
        /// Gets all an enumerator that iterates over all topics of the mind map. This includes the floating topics, 
        /// the main topic and the rest down the tree.
        /// </summary>
        /// <returns>An enumerator that iterates over all topics of the mind map. This includes the floating topics, 
        /// the main topic and the rest down the tree.</returns>
        public IEnumerable<Topic> GetAllTopics()
        {
            var topicStack = new Stack<TopicCollection>();
            topicStack.Push( new TopicCollection { mainTopic } );

            while ( topicStack.Count > 0 )
            {
                TopicCollection topicCollection = topicStack.Pop();
                foreach ( Topic topic in topicCollection )
                {
                    yield return topic;
                    topicStack.Push( topic.SubTopics );
                }
            }

            foreach ( Topic floatingTopic in floatingTopics )
            {
                yield return floatingTopic;
            }
        }

        /// <summary>
        /// Determines whether this <see cref="MindMap"/> contains the topic.
        /// The comparison is performed on a object reference level (with ==).
        /// </summary>
        /// <param name="topic">The topic.</param>
        /// <returns>
        /// 	<c>true</c> if this <see cref="MindMap"/> contains the topic; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsTopic( Topic topic )
        {
            foreach ( Topic existentTopic in GetAllTopics() )
            {
                if ( existentTopic == topic )
                {
                    return true;
                }
            }
            return false;
        }

        private void OnFloatingTopicsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            switch ( e.Action )
            {
                case NotifyCollectionChangedAction.Add :
                    Debug.Assert( e.NewItems != null );
                    Debug.Assert( e.NewItems.Count >= 0 );

                    var newTopics = new Topic[e.NewItems.Count];

                    for ( int i = 0; i < e.NewItems.Count; i++ )
                    {
                        var newTopic = (Topic) e.NewItems[ i ];
                        newTopic.DoActionEvent += ActionHappened;
                        newTopics[ i ] = newTopic;
                    }

                    //TODO: create method to rise the event; think out new name for the event
                    var addFloatingTopicsAction = new AddFloatingTopicsAction( newTopics );
                    if ( ActionHappened != null )
                    {
                        ActionHappened( this, new ActionEventArgs( addFloatingTopicsAction ) );
                    }

                    break;

                case NotifyCollectionChangedAction.Remove :
                    Debug.Assert( e.OldItems != null );
                    Debug.Assert( e.OldItems.Count == 1, "It is expected floating topics to be removed one by one" );

                    var toRemove = (Topic) e.OldItems[ 0 ];
                    toRemove.DoActionEvent -= ActionHappened;
                    var removeTopicAction = new RemoveTopicAction( toRemove.ID );

                    //TODO: create method to rise the event; think out new name for the event
                    if ( ActionHappened != null )
                    {
                        ActionHappened( this, new ActionEventArgs( removeTopicAction ) );
                    }

                    break;

                default :
                    Debug.Fail( "Unrecognized NotifyCollectionChangedAction passed." );
                    break;
            }
        }

        [SuppressMessage( "Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "context" )]
        [OnDeserialized]
        private void OnDeserialized( StreamingContext context )
        {
            floatingTopics.CollectionChanged += OnFloatingTopicsCollectionChanged;
        }

        protected virtual void OnPropertyChanged( PropertyChangedEventArgs args )
        {
            if ( PropertyChanged != null )
            {
                PropertyChanged( this, args );
            }
        }
    }
}
